#include <QCoreApplication>
#include <QAbstractItemModel>
#include <QDateTime>
#include <QRegularExpression>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QThread>
#include <QTemporaryFile>
#include <QIODevice>
#include <QTimer>

#include "setting/JSON_settings.h"
#include "setting/version.h"
#include "ui_ctrl/ui_controller.h"
#include "ui_ctrl/ui_screen_definitions.h"
#include "ui_ctrl/ui_widgets.h"
#include "printer/prt_controller.h"
#include "notecounter/notecounter_controller.h"
#include "controlboard/controlboard.h"
#include "papercontrol/papercontrol.h"
#include "datastorage/datastorage.h"
#include "cloud/cloud_http.h"
#include "coordinator/coordinator.h"
#include "fingerprint/fingerprint.h"
#include "surveillance/surveillance.h"

#define PERIOD_STATUS_CHECK     500                 // 0.5 second
#define PERIOD_TASK_CHECK       200                 // 0.2 second
#define PERIOD_UPLOAD           5000                // 5 seconds

#define WAITING_GATE_TIME       75                  // 15 seconds
#define WAITING_COUNT_TIME      150                 // 30 seconds
#define WAITING_STORE_TIME      100                 // 20 seconds
#define WAITING_INQUIRY_TIME    10                  // 2 seconds


Coordinator *Coordinator::s_coordinator;

Coordinator::Coordinator(QObject *parent)
	: QObject(parent), m_user_name("RongHe")
{
    QThread::sleep(20);

    s_coordinator = this;
    qInfo() << "[COORDINATOR]Software Version:" << sw_version();

    QSettings settings(JSONSettings::GetSettingFilePath(), JSONSettings::GetFormat(), QCoreApplication::instance());

    qRegisterMetaType<NoteInfo>("NoteInfo");
    qRegisterMetaType<TransInfo>("TransInfo");
    qRegisterMetaType<QList<TransInfo>>("QList<TransInfo>");
    qRegisterMetaType<ClearInfo>("ClearInfo");
    qRegisterMetaType<uint16_t>("uint8_t");
    qRegisterMetaType<uint16_t>("uint16_t");
    qRegisterMetaType<uint32_t>("uint32_t");

    m_vendor_name = settings.value(JSON_DEVICE_VENDOR_NAME).toString();
    m_client_name = settings.value(JSON_DEVICE_CLIENT_NAME).toString();
    m_terminal_name = settings.value(JSON_DEVICE_TERMINAL_NAME).toString();
    m_terminal_id = settings.value(JSON_DEVICE_TERMINAL_ID).toString();

    m_http_server_name = settings.value(JSON_HTTP_SERVER_IP).toString();

    m_case_door_gpio_name = settings.value(JSON_GPIO_CASEDOOR).toString();
    m_note_sensor1_gpio_name = settings.value(JSON_GPIO_NOTESENSOR1).toString();
    m_note_sensor2_gpio_name = settings.value(JSON_GPIO_NOTESENSOR2).toString();

    m_user_name = settings.value(JSON_WORK_USERNAME).toString();
    m_use_amount_limit = settings.value(JSON_WORK_USE_AMOUNT_LIMIT).toBool();
    m_curr_amount_limit = settings.value(JSON_WORK_CURR_AMOUNT_LIMIT).toUInt();
    m_max_amount_limit = settings.value(JSON_WORK_MAX_AMOUNT_LIMIT).toUInt();

    m_account_failed_trans = settings.value(JSON_WORK_ACCOUNT_FAILED_TRANS).toBool();

    m_category_list.clear();
    QString category_name;
    CategoryDef category_def;
    category_name = settings.value(JSON_DEPOSIT_CATEGORY1).toString();
    if (!category_name.isEmpty()) {
        category_def.category_code = TRANS_CATEGORY_1;
        category_def.category_desc = category_name;
        m_category_list.append(category_def);
    }
    category_name = settings.value(JSON_DEPOSIT_CATEGORY2).toString();
    if (!category_name.isEmpty()) {
        category_def.category_code = TRANS_CATEGORY_2;
        category_def.category_desc = category_name;
        m_category_list.append(category_def);
    }
    category_name = settings.value(JSON_DEPOSIT_CATEGORY3).toString();
    if (!category_name.isEmpty()) {
        category_def.category_code = TRANS_CATEGORY_3;
        category_def.category_desc = category_name;
        m_category_list.append(category_def);
    }
    category_name = settings.value(JSON_DEPOSIT_CATEGORY4).toString();
    if (!category_name.isEmpty()) {
        category_def.category_code = TRANS_CATEGORY_4;
        category_def.category_desc = category_name;
        m_category_list.append(category_def);
    }
    category_name = settings.value(JSON_DEPOSIT_CATEGORY5).toString();
    if (!category_name.isEmpty()) {
        category_def.category_code = TRANS_CATEGORY_5;
        category_def.category_desc = category_name;
        m_category_list.append(category_def);
    }
    category_name = settings.value(JSON_DEPOSIT_CATEGORY6).toString();
    if (!category_name.isEmpty()) {
        category_def.category_code = TRANS_CATEGORY_6;
        category_def.category_desc = category_name;
        m_category_list.append(category_def);
    }
    category_name = settings.value(JSON_DEPOSIT_CATEGORY7).toString();
    if (!category_name.isEmpty()) {
        category_def.category_code = TRANS_CATEGORY_7;
        category_def.category_desc = category_name;
        m_category_list.append(category_def);
    }
    category_name = settings.value(JSON_DEPOSIT_CATEGORY8).toString();
    if (!category_name.isEmpty()) {
        category_def.category_code = TRANS_CATEGORY_8;
        category_def.category_desc = category_name;
        m_category_list.append(category_def);
    }

	m_client = nullptr;

    m_ui_controller = new UIController(this); emit m_ui_controller->start();
    m_prt_controller = new PrtController(this); emit m_prt_controller->start();
    m_notecounter_controller = new NCController(this); emit m_notecounter_controller->start();
    m_cntlbrd_controller = new CntlBoardController(this); emit m_cntlbrd_controller->start();
    m_paper_controller = new PaperController(this); emit m_paper_controller->start();
    m_fp_controller = new FingerPrintController(this); emit m_fp_controller->start();
    m_surveillance_controller = new SurveillanceController(this), emit m_surveillance_controller->start();

    m_datastorage = new DataStorage(DB_NAME, this);
    m_dev_status.terminal_id = m_terminal_id; 
    m_dev_status.dev_status = DEV_NORMAL;
    m_dev_status.state = DEV_IN_IDLE;
    m_dev_status.bag_status = BAG_EMPTY;
    update_transaction_summary();

    m_http_server = new HttpCloud(this); emit m_http_server->start();

    // because the worker object is in another thread and the new widget
    // controller is to be move to that thread, so it's parent can not
    // be this coordinator object, it must be set to nullptr
    m_widgets = new UIWidgetController(m_ui_controller->getWorker(), nullptr);

    m_component_check_timer = new QTimer(this);
    connect(m_component_check_timer, &QTimer::timeout, this, &Coordinator::component_check);
    m_component_check_timer->start(PERIOD_STATUS_CHECK);
    m_task_timer = new QTimer(this);
    connect(m_task_timer, &QTimer::timeout, this, &Coordinator::task_check);
    m_task_timer->start(PERIOD_TASK_CHECK);
    m_upload_timer = new QTimer(this);
    connect(m_upload_timer, &QTimer::timeout, this, &Coordinator::task_upload);
    m_upload_timer->start(PERIOD_UPLOAD);

    m_user_logged = false;
    m_prt_online = false;
    m_notecounter_online = false;
	m_server_online = false;
    m_bag_gate_opened = true;
	m_case_door_closed = false;

    m_nc_auto_count = settings.value(JSON_WORK_AUTO_COUNT).toBool();
    m_nc_auto_store = settings.value(JSON_WORK_AUTO_STORE).toBool();
    m_nc_fault = false;

    set_task(TASK_NONE, TASK_NONE, WAITING_INQUIRY_TIME);

    // for UI Widget controller
    connect(this, &Coordinator::prt_state_changed, m_widgets, &UIWidgetController::prt_state_changed);
    connect(this, &Coordinator::notecounter_state_changed, m_widgets, &UIWidgetController::notecounter_state_changed);
    connect(this, &Coordinator::server_state_changed, m_widgets, &UIWidgetController::server_state_changed);
    connect(this, &Coordinator::bag_state_changed, m_widgets, &UIWidgetController::bag_state_changed);
    connect(this, &Coordinator::case_door_state_changed, m_widgets, &UIWidgetController::case_door_state_changed);
    connect(this, &Coordinator::admin_logged, m_widgets, &UIWidgetController::admin_logged);
    connect(this, &Coordinator::user_logged, m_widgets, &UIWidgetController::user_logged);
    connect(this, &Coordinator::display_note, m_widgets, &UIWidgetController::display_note);
    connect(this, &Coordinator::store_note_complete, m_widgets, &UIWidgetController::store_note_complete);
    connect(this, &Coordinator::store_coin_complete, m_widgets, &UIWidgetController::store_coin_complete);
    connect(this, &Coordinator::data_show, m_widgets, &UIWidgetController::data_show);
    connect(this, &Coordinator::op_data_show, m_widgets, &UIWidgetController::op_data_show);
    connect(this, &Coordinator::clr_data_show, m_widgets, &UIWidgetController::clr_data_show);
    connect(this, &Coordinator::do_edit_op, m_widgets, &UIWidgetController::do_edit_op);
    connect(this, &Coordinator::set_user_name, m_widgets, &UIWidgetController::set_user_name);
    connect(this, &Coordinator::set_machine_info, m_widgets, &UIWidgetController::set_machine_info);
    connect(this, &Coordinator::clr_total_show, m_widgets, &UIWidgetController::clr_total_show);
    connect(this, SIGNAL(msg_report(uint32_t, uint32_t)), m_widgets, SLOT(set_prompt(uint32_t, uint32_t)));
    connect(this, SIGNAL(msg_report(QString)), m_widgets, SLOT(set_prompt(QString)));
    connect(this, &Coordinator::set_nc_fault_prompt, m_widgets, &UIWidgetController::set_nc_fault_prompt);
    connect(this, &Coordinator::count_starting, m_widgets, &UIWidgetController::count_starting);
    connect(this, &Coordinator::count_completing, m_widgets, &UIWidgetController::count_completing);
    connect(this, &Coordinator::store_starting, m_widgets, &UIWidgetController::store_starting);
    connect(this, &Coordinator::store_completing, m_widgets, &UIWidgetController::store_completing);

    connect(m_widgets, &UIWidgetController::update_component_status, this, &Coordinator::update_component_status);
    connect(m_widgets, &UIWidgetController::enter_screen, this, &Coordinator::enter_screen);
    connect(m_widgets, &UIWidgetController::enter_init_mode, this, &Coordinator::enter_init_mode);
    connect(m_widgets, &UIWidgetController::enter_intelligent_mode, this, &Coordinator::enter_intelligent_mode);
    connect(m_widgets, &UIWidgetController::enter_mixed_mode, this, &Coordinator::enter_mixed_mode);
    connect(m_widgets, &UIWidgetController::enter_count_mode, this, &Coordinator::enter_count_mode);
    connect(m_widgets, &UIWidgetController::enter_note_transaction_mode, this, &Coordinator::enter_note_transaction_mode);
    connect(m_widgets, &UIWidgetController::enter_coin_transaction_mode, this, &Coordinator::enter_coin_transaction_mode);
    connect(m_widgets, &UIWidgetController::user_login, this, &Coordinator::user_login);
    connect(m_widgets, &UIWidgetController::user_logout, this, &Coordinator::user_logout);
    connect(m_widgets, &UIWidgetController::user_modify_password, this, &Coordinator::user_modify_password);
    connect(m_widgets, &UIWidgetController::store_note, this, &Coordinator::store_note);
    connect(m_widgets, &UIWidgetController::store_coin, this, &Coordinator::store_coin);
    connect(m_widgets, &UIWidgetController::print_coin_trans, this, &Coordinator::print_coin_trans);
    connect(m_widgets, &UIWidgetController::transaction_count_info, this, &Coordinator::transaction_count_info);
    connect(m_widgets, &UIWidgetController::finish_transaction, this, &Coordinator::finish_transaction);
    connect(m_widgets, &UIWidgetController::inquiry_data, this, &Coordinator::inquiry_data);
    connect(m_widgets, &UIWidgetController::inquiry_op, this, &Coordinator::inquiry_op);
    connect(m_widgets, &UIWidgetController::add_op, this, &Coordinator::add_op);
    connect(m_widgets, &UIWidgetController::update_op, this, &Coordinator::update_op);
    connect(m_widgets, &UIWidgetController::delete_op, this, &Coordinator::delete_op);
    connect(m_widgets, &UIWidgetController::edit_op, this, &Coordinator::edit_op);
    connect(m_widgets, &UIWidgetController::fp_enroll, this, &Coordinator::fp_enroll);
    connect(m_widgets, &UIWidgetController::print_current_transaction, this, &Coordinator::print_current_transaction);
    connect(m_widgets, &UIWidgetController::print_transaction, this, &Coordinator::print_transaction);
    connect(m_widgets, &UIWidgetController::print_transactions, this, &Coordinator::print_transactions);
    connect(m_widgets, &UIWidgetController::machine_info, this, &Coordinator::machine_info);
    connect(m_widgets, &UIWidgetController::inquiry_clr_data, this, &Coordinator::inquiry_clr_data);
    connect(m_widgets, &UIWidgetController::print_clr_transaction, this, &Coordinator::print_clr_transaction);
    connect(m_widgets, &UIWidgetController::start_clear_deposit, this, &Coordinator::start_clear_deposit);
    connect(m_widgets, &UIWidgetController::clear_deposit, this, &Coordinator::clear_deposit);
    connect(m_widgets, &UIWidgetController::countinue_count, this, &Coordinator::countinue_count);

    // for Note Counter controller
    connect(this, &Coordinator::set_mode, m_notecounter_controller->getWorker(), &NCController_Worker::set_mode);
    connect(this, &Coordinator::start_store, m_notecounter_controller->getWorker(), &NCController_Worker::start_store);
    connect(this, &Coordinator::complete_store, m_notecounter_controller->getWorker(), &NCController_Worker::complete_store);
    connect(this, &Coordinator::clear_transaction, m_notecounter_controller->getWorker(), &NCController_Worker::clear_transaction);
    connect(this, &Coordinator::start_coin_transaction, m_notecounter_controller->getWorker(), &NCController_Worker::start_coin_transaction);
    connect(this, &Coordinator::complete_coin_transaction, m_notecounter_controller->getWorker(), &NCController_Worker::complete_coin_transaction);
    connect(this, &Coordinator::start_count, m_notecounter_controller->getWorker(), &NCController_Worker::start_count);
    connect(this, &Coordinator::inquiry_nc_status, m_notecounter_controller->getWorker(), &NCController_Worker::inquiry_nc_status);
    connect(this, &Coordinator::open_shell, m_notecounter_controller->getWorker(), &NCController_Worker::open_shell);
    connect(this, &Coordinator::close_shell, m_notecounter_controller->getWorker(), &NCController_Worker::close_shell);

    connect(m_notecounter_controller->getWorker(), &NCController_Worker::transaction_count_info, this, &Coordinator::transaction_count_info);
    connect(m_notecounter_controller->getWorker(), &NCController_Worker::count_exception, this, &Coordinator::count_exception);
    connect(m_notecounter_controller->getWorker(), &NCController_Worker::count_started, this, &Coordinator::count_started);
    connect(m_notecounter_controller->getWorker(), &NCController_Worker::count_complete, this, &Coordinator::count_complete);
    connect(m_notecounter_controller->getWorker(), &NCController_Worker::store_exception, this, &Coordinator::store_exception);
    connect(m_notecounter_controller->getWorker(), &NCController_Worker::store_in_progress, this, &Coordinator::store_in_progress);
    connect(m_notecounter_controller->getWorker(), &NCController_Worker::store_complete, this, &Coordinator::store_complete);
    connect(m_notecounter_controller->getWorker(), &NCController_Worker::nc_status, this, &Coordinator::nc_status);
    connect(m_notecounter_controller->getWorker(), &NCController_Worker::nc_error, this, &Coordinator::nc_error);

    connect(this, &Coordinator::auto_countinue_count, this, &Coordinator::countinue_count, Qt::QueuedConnection);
    connect(this, &Coordinator::auto_store, this, &Coordinator::store_note, Qt::QueuedConnection);

    // for Printer controller
    connect(this, &Coordinator::print, m_prt_controller->getWorker(), &PrtController_Worker::print_transaction);
    connect(this, &Coordinator::print_all, m_prt_controller->getWorker(), &PrtController_Worker::print_transactions);
    connect(this, &Coordinator::clr_print, m_prt_controller->getWorker(), &PrtController_Worker::print_clr_transaction);

    // for Control Board controller
    connect(this, &Coordinator::open_store_gate, m_cntlbrd_controller->getWorker(), &ControlBoard_Worker::open_store_gate);
    connect(this, &Coordinator::close_store_gate, m_cntlbrd_controller->getWorker(), &ControlBoard_Worker::close_store_gate);
    connect(this, &Coordinator::turn_on_fans, m_cntlbrd_controller->getWorker(), &ControlBoard_Worker::turn_on_fans);
    connect(this, &Coordinator::turn_off_fans, m_cntlbrd_controller->getWorker(), &ControlBoard_Worker::turn_off_fans);
    connect(this, &Coordinator::takeout_paper, m_cntlbrd_controller->getWorker(), &ControlBoard_Worker::takeout_paper);
    connect(this, &Coordinator::prepare_store, m_cntlbrd_controller->getWorker(), &ControlBoard_Worker::prepare_store);
    connect(this, &Coordinator::store_paper, m_cntlbrd_controller->getWorker(), &ControlBoard_Worker::store_paper);
    connect(m_cntlbrd_controller->getWorker(), &ControlBoard_Worker::status, this, &Coordinator::store_gate_status);

    // for Paper controller
    connect(this, &Coordinator::paper_action, m_paper_controller->getWorker(), &PaperControl_Worker::action);
    connect(m_paper_controller->getWorker(), &PaperControl_Worker::status, this, &Coordinator::store_gate_status);
    connect(m_notecounter_controller->getWorker(), &NCController_Worker::paper_action, m_paper_controller->getWorker(), &PaperControl_Worker::action);

    // for HTTP connection
    connect(this, &Coordinator::upload_transaction, m_http_server->getWorker(), &HttpCloud_Worker::upload_transaction);
    connect(m_http_server->getWorker(), &HttpCloud_Worker::transaction_uploaded, this, &Coordinator::transaction_uploaded);
    connect(m_http_server->getWorker(), &HttpCloud_Worker::sw_upgrade, this, &Coordinator::sw_upgrade);

    // for Fingerprint controller
    connect(this, &Coordinator::fp_detect_start, m_fp_controller->getWorker(), &FingerPrint_Worker::fp_detect_start);
    connect(this, &Coordinator::fp_detect_stop, m_fp_controller->getWorker(), &FingerPrint_Worker::fp_detect_stop);
    connect(this, &Coordinator::fp_enroll_start, m_fp_controller->getWorker(), &FingerPrint_Worker::fp_enroll_start);
    connect(this, &Coordinator::fp_save, m_fp_controller->getWorker(), &FingerPrint_Worker::fp_save);
    connect(this, &Coordinator::fp_delete, m_fp_controller->getWorker(), &FingerPrint_Worker::fp_delete);
    connect(m_fp_controller->getWorker(), &FingerPrint_Worker::fp_dected, this, &Coordinator::fp_dected);
    connect(m_fp_controller->getWorker(), &FingerPrint_Worker::fp_message, this, &Coordinator::fp_message);

    connect(m_fp_controller->getWorker(), &FingerPrint_Worker::fp_message, m_widgets, &UIWidgetController::fp_message);

    // for Surveillance controller
    connect(this, &Coordinator::start_video_record, m_surveillance_controller->getWorker(), &Surveillance_Worker::start_video_record);
    connect(this, &Coordinator::stop_video_record, m_surveillance_controller->getWorker(), &Surveillance_Worker::stop_video_record);

    QTimer::singleShot(100, this, &Coordinator::start_work);
}

Coordinator::~Coordinator()
{
    if (m_datastorage)
        delete m_datastorage;
    if (m_ui_controller)
		delete m_ui_controller;
	if (m_prt_controller)
		delete m_prt_controller;
	if (m_notecounter_controller)
		delete m_notecounter_controller;
}

void Coordinator::set_auto_count(bool on)
{
    QSettings settings(JSONSettings::GetSettingFilePath(), JSONSettings::GetFormat(), QCoreApplication::instance());
    m_nc_auto_count = on;
    settings.setValue(JSON_WORK_AUTO_COUNT, on);
}

void Coordinator::set_auto_store(bool on)
{
    QSettings settings(JSONSettings::GetSettingFilePath(), JSONSettings::GetFormat(), QCoreApplication::instance());
    m_nc_auto_store = on;
    settings.setValue(JSON_WORK_AUTO_STORE, on);
}

void Coordinator::set_use_amount_limit(bool on)
{
    QSettings settings(JSONSettings::GetSettingFilePath(), JSONSettings::GetFormat(), QCoreApplication::instance());
    m_use_amount_limit = on;
    settings.setValue(JSON_WORK_USE_AMOUNT_LIMIT, on);
}

void Coordinator::set_account_failed_trans(bool on)
{
    QSettings settings(JSONSettings::GetSettingFilePath(), JSONSettings::GetFormat(), QCoreApplication::instance());
    m_account_failed_trans = on;
    settings.setValue(JSON_WORK_ACCOUNT_FAILED_TRANS, on);
}

void Coordinator::start_work()
{
    // remove all enroll fingerprint templates not used by operator database
    fp_cleanup();

    emit set_user_name(m_user_name);
    emit close_store_gate();
    emit turn_off_fans();
    emit set_machine_info(m_terminal_name, m_http_server_name, m_terminal_id);
}

void Coordinator::component_check()
{
    bool online, opened, closed;

    online = m_http_server->getWorker()->online();
    if (online != m_server_online) {
        m_server_online = online;
        qInfo() << "[COORDINATOR]Server Online:" << online;
        emit server_state_changed(m_server_online);
    }

    online = m_prt_controller->getWorker()->online();
    if (online != m_prt_online) {
        m_prt_online = online;
        qInfo() << "[COORDINATOR]Printer Online:" << online;
        emit prt_state_changed(m_prt_online);
    }

    online = m_notecounter_controller->getWorker()->online();
    if (online != m_notecounter_online) {
        m_notecounter_online = online;
        qInfo() << "[COORDINATOR]NoteCounter Online:" << online;
        emit notecounter_state_changed(m_notecounter_online);
    }

    opened = m_cntlbrd_controller->getWorker()->gate_opened();
    if (opened != m_bag_gate_opened) {
        m_bag_gate_opened = opened;
        qInfo() << "[COORDINATOR]Bag Gate Opened:" << opened;
        emit bag_state_changed(m_bag_gate_opened);
    }

    closed = gpio_get(m_case_door_gpio_name) == 0;
    if (closed != m_case_door_closed) {
        m_case_door_closed = closed;
        qInfo() << "[COORDINATOR]Case Door Closed" << closed;
        emit case_door_state_changed(m_case_door_closed);
    }
    m_sensor1_no_note= gpio_get(m_note_sensor1_gpio_name) == 0;
    m_sensor2_no_note = gpio_get(m_note_sensor2_gpio_name) == 0;
}

void Coordinator::task_check()
{
    uint32_t saved_note_count;

    switch ((uint32_t) m_task.curr_task) {
    case TASK_OPEN_BAG_GATE:
        if (!m_bag_gate_opened) {
            m_task.waiting_time--;
            if (m_task.waiting_time == 0) {
                qInfo() << "[COORDINATOR]Store Failed";
                set_task(TASK_NONE, TASK_NONE, 2 * WAITING_INQUIRY_TIME);
                emit store_fail();
                emit close_store_gate();
                emit turn_off_fans();
                emit msg_report(MSG_ERROR, MSG_nc_store_failed);
            }
        }
        else {
            qInfo() << "[COORDINATOR]Store Start";
            set_task(m_task.next_task, TASK_NONE, WAITING_STORE_TIME);
            switch ((uint32_t) m_task.curr_task) {
            case TASK_STORE_NOTE:
                emit start_store();
                break;
            case TASK_STORE_COIN:
                emit start_store();
                break;
            }
            emit msg_report(MSG_INFO, MSG_nc_store_started);
        }
        break;
    case TASK_STORE_NOTE:
        m_task.waiting_time--;
        if (m_task.store_exception || m_task.store_completed || m_task.waiting_time == 0) {
            m_last_subtrans = m_curr_trans;
            if (m_task.store_exception || m_task.waiting_time == 0) {
                emit close_store_gate();
                emit turn_off_fans();
                if (m_account_failed_trans)
                    save_transaction(m_widgets->trans_type());
                emit finish_transaction(m_task.store_cmd, m_task.store_exception);
                if (m_task.waiting_time == 0)
                    emit msg_report(MSG_ERROR, MSG_nc_store_timeout);
            }
            else {
                emit complete_store();
                emit store_note_complete();
                saved_note_count = save_transaction(m_widgets->trans_type());
                if (saved_note_count != 100)
                    emit finish_transaction();
                else if (m_nc_auto_count)
                    QTimer::singleShot(1000, this, &Coordinator::countinue_count);
            }
            set_task(TASK_NONE, TASK_NONE, 2 * WAITING_INQUIRY_TIME);
            emit store_completing();
        }
        break;
    case TASK_STORE_COIN:
        m_task.waiting_time--;
        if (m_task.store_exception || m_task.store_completed || m_task.waiting_time == 0) {
            m_last_subtrans = m_curr_trans;
            if (m_task.store_exception || m_task.waiting_time == 0) {
                emit close_store_gate();
                emit turn_off_fans();
                if (m_account_failed_trans)
                    save_transaction(m_widgets->trans_type());
                emit finish_transaction(m_task.store_cmd, m_task.store_exception);
                if (m_task.waiting_time == 0)
                    emit msg_report(MSG_ERROR, MSG_nc_store_timeout);
            }
            else {
                emit complete_store();
                emit store_coin_complete();
                save_transaction(m_widgets->trans_type());
                finish_transaction();
            }
            emit store_completing();
            enter_init_mode();
        }
        break;
    case TASK_COUNT:
        m_task.waiting_time--;
        if (m_task.count_exception
                || (m_task.count_started && m_task.count_completed)
                || (!m_task.count_started && (m_task.waiting_time < (WAITING_COUNT_TIME * 4) / 5))
                || m_task.waiting_time == 0) {
            emit count_completing();
            if (m_nc_auto_store && m_notecounter_controller->getWorker()->transaction_mode() == NCController_Worker::MODE_NOTE_TRANSACTION)
                if (m_task.count_started && m_task.count_completed && m_curr_trans.note_info.total_count == 100)
                    emit auto_store();
            set_task(TASK_NONE, TASK_NONE, 2 * WAITING_INQUIRY_TIME);
        }
        break;
    case TASK_NONE:
        m_task.waiting_time--;
        if (m_task.waiting_time == 0) {
            m_task.waiting_time = WAITING_INQUIRY_TIME;
            emit inquiry_nc_status();
            m_task.inquiry_count++;
            if (m_task.inquiry_count < 2)
                break;
            else
                m_task.inquiry_count = 2;

            if (m_notecounter_controller->getWorker()->online())
                switch (m_notecounter_controller->getWorker()->transaction_mode()) {
                case NCController_Worker::MODE_INTELLIGENT:
                case NCController_Worker::MODE_MIXED:
                case NCController_Worker::MODE_COUNT:
                    if (m_notecounter_controller->getWorker()->shell_opened()
                            && m_notecounter_controller->getWorker()->collect_empty()) {
                        if (!m_notecounter_controller->getWorker()->reject_empty())
                            emit msg_report(MSG_ERROR, MSG_nc_reject_not_empty);
                        else {
                            emit close_shell();
                            m_task.waiting_time = 2 * WAITING_INQUIRY_TIME;
                        }
                    }
                    else if (!m_notecounter_controller->getWorker()->shell_opened()
                            && m_notecounter_controller->getWorker()->collect_empty()
                            && !m_notecounter_controller->getWorker()->entry_empty()) {
                        if (!m_notecounter_controller->getWorker()->reject_empty())
                            emit msg_report(MSG_ERROR, MSG_nc_reject_not_empty);
                        else {
                            emit clear_transaction();
                            emit start_count();
                            set_task(TASK_COUNT, TASK_NONE, WAITING_COUNT_TIME);
                            emit count_starting();
                        }
                    }
                    break;
                case NCController_Worker::MODE_INIT:
                    if (m_notecounter_controller->getWorker()->shell_opened()
                            && m_notecounter_controller->getWorker()->collect_empty()) {
                        emit close_shell();
                        m_task.waiting_time = 2 * WAITING_INQUIRY_TIME;
                    }
                    else if (!m_notecounter_controller->getWorker()->shell_opened()
                            && !m_notecounter_controller->getWorker()->collect_empty()) {
                        emit open_shell();
                        m_task.waiting_time = 2 * WAITING_INQUIRY_TIME;
                    }
                    if (!m_notecounter_controller->getWorker()->reject_empty())
                        emit msg_report(MSG_ERROR, MSG_nc_reject_not_empty);
                    if (!m_notecounter_controller->getWorker()->shell_opened()
                            && m_notecounter_controller->getWorker()->collect_empty()) {
                        if (m_stop_inquiry_countdown == 0) {
                            m_task.waiting_time = 120 * 30 * WAITING_INQUIRY_TIME;       // 120 minutes
                            qInfo() << "[COORDINATOR]Stop NoteCounter status inquiry!";
                        }
                        else {
                            m_stop_inquiry_countdown--;
                            qInfo() << "[COORDINATOR]Inquriy Count-Down!" << m_stop_inquiry_countdown;
                        }
                    }
                    else
                        m_stop_inquiry_countdown = 20;
                    break;
                case NCController_Worker::MODE_NOTE_TRANSACTION:
                case NCController_Worker::MODE_COIN_TRANSACTION:
                    break;
                }
        }
        break;
    }
}

void Coordinator::task_upload()
{
    QList<TransInfo> trans_info_list;

    if (m_datastorage->db_query_not_uploaded(trans_info_list)) {
        foreach(auto trans_info, trans_info_list) {
            trans_info.user_alias = m_datastorage->db_op_station_id(trans_info.user_name);
            emit upload_transaction(trans_info);
        }
    }
}

void Coordinator::update_component_status()
{
	emit prt_state_changed(m_prt_online);
	emit notecounter_state_changed(m_notecounter_online);
	emit server_state_changed(m_server_online);
	emit bag_state_changed(m_bag_gate_opened);
	emit case_door_state_changed(m_case_door_closed);
}

void Coordinator::enter_screen(uint32_t scrn_id)
{
    if (scrn_id == SCRN_main) {
        m_user_logged = false;
        emit fp_detect_start();
    }
    else
        emit fp_detect_stop();
}

void Coordinator::enter_init_mode()
{
    qInfo() << "[COORDINATOR]Enter Initial Mode";
    set_task(TASK_NONE, TASK_NONE, WAITING_INQUIRY_TIME);
    emit set_mode(NCController_Worker::MODE_INIT);
    emit close_store_gate();
    emit turn_off_fans();
}

void Coordinator::enter_intelligent_mode()
{
#ifdef __aarch64__
    if (!m_sensor1_no_note || !m_sensor2_no_note) {
        emit msg_report(MSG_ERROR, MSG_bag_sensor_fail);
        return;
    }
#endif

    emit m_widgets->set_screen(SCRN_intelligent_mode);

    qInfo() << "[COORDINATOR]Enter Intelligent Mode";
    set_task(TASK_COUNT, TASK_NONE, WAITING_COUNT_TIME);
    emit set_mode(NCController_Worker::MODE_INTELLIGENT);
    emit count_starting();
    memset(&m_accumulated_trans.note_info, 0, sizeof(NoteInfo));
    memset(&m_curr_trans.note_info, 0, sizeof(NoteInfo));
    if (m_nc_fault) {
//        QTimer::singleShot(2000, this, [this] { emit start_count(); });
        m_nc_fault = false;
    }
}

void Coordinator::enter_mixed_mode()
{
#ifdef __aarch64__
    if (!m_sensor1_no_note || !m_sensor2_no_note) {
        emit msg_report(MSG_ERROR, MSG_bag_sensor_fail);
        return;
    }
#endif

    emit m_widgets->set_screen(SCRN_mixed_mode);

    qInfo() << "[COORDINATOR]Enter Mixed Mode";
    set_task(TASK_COUNT, TASK_NONE, WAITING_COUNT_TIME);
    emit set_mode(NCController_Worker::MODE_MIXED);
    emit count_starting();
    memset(&m_accumulated_trans.note_info, 0, sizeof(NoteInfo));
    memset(&m_curr_trans.note_info, 0, sizeof(NoteInfo));
    if (m_nc_fault) {
        QTimer::singleShot(2000, this, [this] { emit start_count(); });
        m_nc_fault = false;
    }
}

void Coordinator::enter_count_mode()
{
#ifdef __aarch64__
    if (!m_sensor1_no_note || !m_sensor2_no_note) {
        emit msg_report(MSG_ERROR, MSG_bag_sensor_fail);
        return;
    }
#endif

    emit m_widgets->set_screen(SCRN_count_mode);

    qInfo() << "[COORDINATOR]Enter Count Mode";
    set_task(TASK_COUNT, TASK_NONE, WAITING_COUNT_TIME);
    memset(&m_accumulated_trans.note_info, 0, sizeof(NoteInfo));
    memset(&m_curr_trans.note_info, 0, sizeof(NoteInfo));
    emit set_mode(NCController_Worker::MODE_COUNT);
    emit count_starting();
    if (m_nc_fault) {
        QTimer::singleShot(2000, this, [this] { emit start_count(); });
        m_nc_fault = false;
    }
}

void Coordinator::enter_note_transaction_mode()
{
#ifdef __aarch64__
    if (!m_sensor1_no_note || !m_sensor2_no_note) {
        emit msg_report(MSG_ERROR, MSG_bag_sensor_fail);
        return;
    }
    if (m_bag_gate_opened) {
        emit msg_report(MSG_ERROR, MSG_bag_door_opened);
        return;
    }
    if (!m_case_door_closed) {
        emit msg_report(MSG_ERROR, MSG_case_door_opened);
        return;
    }
    if (m_notecounter_controller->getWorker()->shell_opened()) {
        emit msg_report(MSG_ERROR, MSG_nc_collect_full);
        return;
    }
    if (!m_notecounter_online) {
        emit msg_report(MSG_ERROR, MSG_nc_offline);
        return;
    }
#endif

    emit m_widgets->set_screen(SCRN_deposit_note);

    qInfo() << "[COORDINATOR]Enter Note Transaction Mode";
    set_task(TASK_COUNT, TASK_NONE, WAITING_COUNT_TIME);
    emit set_mode(NCController_Worker::MODE_NOTE_TRANSACTION);
    emit count_starting();
    memset(&m_accumulated_trans.note_info, 0, sizeof(NoteInfo));
    memset(&m_curr_trans.note_info, 0, sizeof(NoteInfo));
    m_surveillance_controller->getWorker()->start_video_record();
    if (m_nc_fault) {
        QTimer::singleShot(2000, this, [this] { emit start_count(); });
        m_nc_fault = false;
    }
}

void Coordinator::enter_coin_transaction_mode()
{
#ifdef __aarch64__
    if (!m_sensor1_no_note || !m_sensor2_no_note) {
        emit msg_report(MSG_ERROR, MSG_bag_sensor_fail);
        return;
    }
    if (m_bag_gate_opened) {
        emit msg_report(MSG_ERROR, MSG_bag_door_opened);
        return;
    }
    if (!m_case_door_closed) {
        emit msg_report(MSG_ERROR, MSG_case_door_opened);
        return;
    }
    if (m_notecounter_controller->getWorker()->shell_opened()) {
        emit msg_report(MSG_ERROR, MSG_nc_collect_not_empty);
        return;
    }
    if (!m_notecounter_online) {
        emit msg_report(MSG_ERROR, MSG_nc_offline);
        return;
    }
#endif

    emit m_widgets->set_screen(SCRN_deposit_coin);

    qInfo() << "[COORDINATOR]Enter Cash Transaction Mode";
    set_task(TASK_COUNT, TASK_NONE, WAITING_INQUIRY_TIME);
    emit set_mode(NCController_Worker::MODE_COIN_TRANSACTION);
    memset(&m_accumulated_trans.note_info, 0, sizeof(NoteInfo));
    memset(&m_curr_trans.note_info, 0, sizeof(NoteInfo));

    m_surveillance_controller->getWorker()->start_video_record();
}

void Coordinator::countinue_count()
{
    switch (m_notecounter_controller->getWorker()->transaction_mode()) {
    case NCController_Worker::MODE_COUNT:
    case NCController_Worker::MODE_INTELLIGENT:
    case NCController_Worker::MODE_MIXED:
        if (m_curr_trans.note_info.total_count >= 100) {
            emit open_shell();
            return;
        }
        break;
    case NCController_Worker::MODE_NOTE_TRANSACTION:
        if (m_curr_trans.note_info.total_count >= 100) {
            emit msg_report(MSG_ERROR, MSG_nc_collect_full);
            return;
        }
        break;
    default:
        return;
    }
    emit start_count();
    emit count_starting();

    set_task(TASK_COUNT, TASK_NONE, WAITING_COUNT_TIME);
}

void Coordinator::count_exception(uint16_t cmd, uint16_t exception_code, QString exception_name)
{
    Q_UNUSED(cmd)

    qInfo() << "[COORDINATOR]Count Exception" << exception_code << exception_name;

    switch (exception_code) {
    case MSG_nc_entry_empty:
        emit msg_report(MSG_ERROR, MSG_nc_entry_empty);
        break;
    case MSG_nc_entry_not_empty:
        emit msg_report(MSG_ERROR, MSG_nc_entry_not_empty);
        break;
    case MSG_nc_reject_not_empty:
        emit msg_report(MSG_ERROR, MSG_nc_reject_not_empty);
        break;
    case MSG_nc_collect_not_empty:
        emit msg_report(MSG_ERROR, MSG_nc_collect_not_empty);
        break;
    default:
        emit msg_report(exception_name);
        break;
    }

    m_task.count_exception = exception_code;
}

void Coordinator::count_started()
{
    m_task.count_started = true;
}

void Coordinator::count_complete(uint16_t status)
{
    Q_UNUSED(status)

    m_task.count_completed = true;
    if (m_curr_trans.note_info.total_count >= 100) {
        m_accumulated_trans.note_info.total_count += m_curr_trans.note_info.total_count;
        m_accumulated_trans.note_info.total_amount += m_curr_trans.note_info.total_amount;
        for (int k = 0; k < NOTE_TYPE_COUNT; k++) {
            m_accumulated_trans.note_info.note_count[k] += m_curr_trans.note_info.note_count[k];
            m_accumulated_trans.note_info.note_amount[k] += m_curr_trans.note_info.note_amount[k];
        }
    }
}

void Coordinator::user_login(QString user_name, QString password)
{
    if (m_user_logged)
        return;
    if (user_name.isEmpty() || password.isEmpty()) {
        msg_report(MSG_ERROR, MSG_user_name_password_empty);
        return;
    }

    qInfo() << "[COORDINATOR]User Login (username:" << user_name << " password:" << password << ")";

    if (!m_datastorage->db_op_authenticate(user_name, password)) {
        emit msg_report(MSG_ERROR, MSG_user_authenticate_fail);
        return;
    }

    QSettings settings(JSONSettings::GetSettingFilePath(), JSONSettings::GetFormat(), QCoreApplication::instance());
    settings.setValue(JSON_WORK_USERNAME, user_name);

    m_user_name = user_name;
    m_user_password = password;
    m_curr_trans.user_name = user_name;
    m_curr_trans.client_name = m_client_name;
    m_curr_trans.terminal_name = m_terminal_name;
    m_curr_trans.terminal_id = m_terminal_id;

    emit set_user_name(m_user_name);

    m_user_logged = true;
    if (m_user_name == ADMIN_USER_NAME)
        emit admin_logged();
    else
    	emit user_logged(); 
}

void Coordinator::user_logout()
{
    qInfo() << "[COORDINATOR]User logout";

    QThread::msleep(1000);
}

void Coordinator::user_modify_password(QString user_name, QString old_password, QString new_password)
{
    if (old_password == new_password) {
        msg_report(MSG_ERROR, MSG_password_same_fail);
        return;
    }
    if (new_password.isEmpty()) {
        msg_report(MSG_ERROR, MSG_user_name_password_empty);
        return;
    }
    qInfo() << "[COORDINATOR]Password Modification (username:" << user_name << " old password:" << old_password << " new password:" << new_password << ")";

    if (!m_datastorage->db_op_authenticate(user_name, old_password)) {
        qInfo() << "[COORDINATOR]Password Modification Failed";
        emit msg_report(MSG_ERROR, MSG_password_modify_fail);
        return;
    }
    qInfo() << "[COORDINATOR]Password Modification Succeed";
    m_datastorage->db_op_update_password(user_name, new_password);

    emit m_widgets->pop_screen();
}

void Coordinator::transaction_count_info(NoteInfo note_info)
{
    NoteInfo accum_note_info;

    note_info.total_amount = 0;
    for (int k = 0; k < NOTE_TYPE_COUNT; k++) {
        note_info.note_amount[k] = note_info.note_count[k] * NOTE_factor(k);
        note_info.total_amount += note_info.note_amount[k];
    }
    m_curr_trans.note_info = note_info;
    accum_note_info = m_accumulated_trans.note_info;
    accum_note_info.total_count += m_curr_trans.note_info.total_count;
    accum_note_info.total_amount += m_curr_trans.note_info.total_amount;
    for (int k = 0; k < NOTE_TYPE_COUNT; k++) {
        accum_note_info.note_count[k] += m_curr_trans.note_info.note_count[k];
        accum_note_info.note_amount[k] += m_curr_trans.note_info.note_amount[k];
    }

    emit display_note(accum_note_info);
}

void Coordinator::store_note()
{
    qInfo() << "[COORDINATOR]Store notes";

    if (m_curr_trans.note_info.total_amount == 0) {
        qInfo() << "[COORDINATOR]Empty Transaction";
        return;
    }
    else if (m_use_amount_limit && m_curr_trans.note_info.total_amount > m_curr_amount_limit) {
        qInfo() << "[COORDINATOR]Note Amount Limit Reached!"
                << QString("%1").arg(m_curr_trans.note_info.total_amount / 100.0, 8, 'f', 2)
                << QString("%1").arg(m_curr_amount_limit / 100.0, 8, 'f', 2);
        msg_report(MSG_ERROR, MSG_nc_amount_limit);
        return;
    }

    emit open_store_gate();
    emit turn_on_fans();
    emit msg_report(MSG_INFO, MSG_waiting_bag_door_open);
    emit store_starting();

    set_task(TASK_OPEN_BAG_GATE, TASK_STORE_NOTE, WAITING_GATE_TIME);
}

void Coordinator::print_coin_trans()
{
    TransInfo coin_trans;
    
    coin_trans.date_time = QDateTime::currentDateTime();
    coin_trans.user_name = m_user_name;
    coin_trans.user_alias = m_datastorage->db_op_station_id(m_user_name);
    coin_trans.client_name = m_client_name;
    coin_trans.terminal_name = m_terminal_name;
    coin_trans.terminal_id = m_terminal_id;

    coin_trans.note_info = m_curr_trans.note_info;

    emit print(coin_trans);
}

void Coordinator::store_coin()
{
    qInfo() << "[COORDINATOR]Store coin";

    if (m_curr_trans.note_info.total_amount == 0) {
        qInfo() << "[COORDINATOR]Empty Transaction";
        return;
    }
    else if (m_use_amount_limit && m_curr_trans.note_info.total_amount > m_curr_amount_limit) {
        qInfo() << "[COORDINATOR]Note Amount Limit Reached!"
                << QString("%1").arg(m_curr_trans.note_info.total_amount / 100.0, 8, 'f', 2)
                << QString("%1").arg(m_curr_amount_limit / 100.0, 8, 'f', 2);
        msg_report(MSG_ERROR, MSG_nc_amount_limit);
        return;
    }

    if (m_notecounter_controller->getWorker()->collect_empty()) {
        emit msg_report(MSG_ERROR, MSG_nc_collect_empty);
        return;
    }

    emit start_coin_transaction();
    emit open_store_gate();
    emit turn_on_fans();
    emit msg_report(MSG_INFO, MSG_waiting_bag_door_open);
    emit store_starting();

    set_task(TASK_OPEN_BAG_GATE, TASK_STORE_COIN, WAITING_GATE_TIME);
}

void Coordinator::store_in_progress()
{
    emit msg_report(MSG_INFO, MSG_nc_store_started);
    m_task.store_completed = false;
}

void Coordinator::store_complete(uint16_t status)
{
    Q_UNUSED(status)

    emit msg_report(MSG_INFO, MSG_nc_store_success);
    m_task.store_completed = true;
}

void Coordinator::store_exception(uint16_t cmd, uint16_t exception_code, QString exception_name)
{
    qInfo() << "[COORDINATOR]Store Exception" << exception_code << exception_name;

    switch (exception_code) {
    case MSG_nc_entry_not_empty:
        emit msg_report(MSG_ERROR, MSG_nc_entry_not_empty);
        break;
    case MSG_nc_reject_not_empty:
        emit msg_report(MSG_ERROR, MSG_nc_reject_not_empty);
        break;
    case MSG_nc_collect_empty:
        emit msg_report(MSG_ERROR, MSG_nc_collect_empty);
        break;
    case MSG_nc_store_failed:
        emit msg_report(MSG_ERROR, MSG_nc_store_failed);
        break;
    default:
        emit msg_report(exception_name);
        break;
    }

    m_task.store_cmd = cmd;
    m_task.store_exception = exception_code;
}

bool Coordinator::nc_in_operation()
{
    return (m_task.curr_task != TASK_NONE);
}

void Coordinator::finish_transaction(uint8_t store_cmd, uint8_t store_exception)
{
    m_datastorage->db_merge_sub_transaction(m_last_saved_trans);
    update_transaction_summary();

    m_surveillance_controller->getWorker()->stop_video_record();

    emit close_store_gate();
    emit turn_off_fans();

    if (store_cmd == CNTR_COMPLETE_STORE_NOTE_CMD && store_exception != CNTR_STATUS_SUCCEED) {
        save_failed_transaction(m_widgets->trans_type());
        emit m_widgets->set_screen(SCRN_store_fail);
    }
    else {
        if (m_last_saved_trans.note_info.total_amount != 0)
            emit m_widgets->set_screen(SCRN_print);
        else
            emit m_widgets->pop_screen();
    }
}

void Coordinator::nc_error(uint16_t error1, uint16_t error2)
{
    switch (m_notecounter_controller->getWorker()->transaction_mode()) {
    case NCController_Worker::MODE_NOTE_TRANSACTION:
    case NCController_Worker::MODE_COIN_TRANSACTION:
        finish_transaction();
        enter_init_mode();
        emit set_nc_fault_prompt(error1, error2);
        m_nc_fault = true;
        break;
    }
}

void Coordinator::nc_status(uint16_t status)
{
    Q_UNUSED(status)
}

void Coordinator::inquiry_data(QDate start_date, QDate stop_date)
{
    TransInfo trans_info;

    qInfo() << "[COORDINATOR]Data Inquiry (Date:" << start_date << "to:" << stop_date << ")";

    m_datastorage->db_query(start_date, stop_date);

    QStringList data_list;
    unsigned int seq = 1;
    while (m_datastorage->db_query_next(trans_info)) {
        QString data;

        data += QString("%1;").arg(seq++);
        data += QString("%1;").arg(trans_info.id);
        data += QString("%1;").arg(((trans_info.note_info.trans_type % 2) == TRANS_CLASS_NOTE) ? tr("Note") : tr("Coin"));
        data += trans_info.user_name + ";";
        data += QString("%1;").arg((trans_info.status) ? tr("Uploaded") : tr("Not Uploaded"));
        data += QString("%1;").arg(trans_info.note_info.total_amount / 100.0, 10, 'f', 2);
        data += trans_info.date_time.toString("yyyy-MM-dd hh:mm:ss");

        data_list << data;
    }
    emit data_show(data_list); 
}

void Coordinator::inquiry_op()
{
    OpInfo op_info;

    qInfo() << "[COORDINATOR]Operator Inquiry";

    m_datastorage->db_op_query();

    QStringList data_list;
    while (m_datastorage->db_op_query_next(op_info)) {
        QString data;

        data += QString("%1;").arg(op_info.op_name);
        data += QString("%1;").arg(op_info.op_password);
        data += QString("%1;").arg(op_info.terminal_name);
        data += QString("%1;").arg(op_info.op_finger_id);

        data_list << data;
    }
    emit op_data_show(data_list); 
}

void Coordinator::add_op(QString user_name, QString user_password, QString station_id)
{
    uint32_t finger_id;
    
    if (user_name.isEmpty() || user_password.isEmpty()) {
        emit msg_report(MSG_ERROR, MSG_user_name_password_empty);
        return;
    }
    if (m_datastorage->db_op_exists(user_name)) {
        emit msg_report(MSG_ERROR, MSG_user_name_exist);
        return;
    }
    qInfo().noquote() << QString("[COORDINATOR]Add operator (%1, %2, %3)").arg(user_name, user_password).arg(station_id);

    if (!m_fp_controller->getWorker()->new_template_avail())
        finger_id = 0;
    else
        finger_id = m_fp_controller->getWorker()->avail_id();
    if (!m_datastorage->db_op_append(user_name, user_password, station_id, finger_id)) {
        emit msg_report(MSG_ERROR, MSG_user_add_fail);
        return;
    }
    if (m_fp_controller->getWorker()->new_template_avail())
        emit fp_save(finger_id);

    emit m_widgets->pop_screen();
    inquiry_op();
}

void Coordinator::update_op(QString user_name, QString user_password, QString station_id, uint32_t finger_id)
{
    if (user_name.isEmpty() || user_password.isEmpty()) {
        emit msg_report(MSG_ERROR, MSG_user_name_password_empty);
        return;
    }
    qInfo().noquote() << QString("[COORDINATOR]Add operator (%1, %2, %3, %4)").arg(user_name, user_password).arg(station_id, finger_id);

    if (m_fp_controller->getWorker()->new_template_avail() && finger_id == 0)
        finger_id = m_fp_controller->getWorker()->avail_id();
    if (!m_datastorage->db_op_update(user_name, user_password, station_id, finger_id)) {
        emit msg_report(MSG_ERROR, MSG_user_update_fail);
        return;
    }
    if (m_fp_controller->getWorker()->new_template_avail())
        emit fp_save(finger_id);

    emit m_widgets->pop_screen();
    inquiry_op();
}

void Coordinator::delete_op(uint32_t user_idx)
{
    OpInfo op_info;

    qInfo().noquote() << QString("[COORDINATOR]Delete %1th operator of inquiry data").arg(user_idx);

    if (m_datastorage->db_op_query_nth(user_idx, op_info)) {
        m_datastorage->db_op_delete(op_info);
        if (op_info.op_finger_id != 0)
            emit fp_delete(op_info.op_finger_id);
    }

    inquiry_op();
}

void Coordinator::edit_op(uint32_t user_idx)
{
    OpInfo op_info;

    if (m_datastorage->db_op_query_nth(user_idx, op_info))
        emit do_edit_op(op_info.op_name, op_info.op_password, op_info.terminal_name, op_info.op_finger_id);
}

void Coordinator::fp_dected(int id)
{
    QString op_name, op_password;

    qInfo() << "[COORDINATOR]User with fingerprint ID" << id << "detected";
    if (m_datastorage->db_op_info(id, op_name, op_password))
        user_login(op_name, op_password);
}

void Coordinator::fp_message(uint32_t proc, uint32_t enroll_step)
{
    Q_UNUSED(proc)
    Q_UNUSED(enroll_step)
}

void Coordinator::fp_enroll(QString user_name)
{
    Q_UNUSED(user_name)

    emit fp_enroll_start();
}

void Coordinator::print_current_transaction()
{
    TransInfo trans_info;

    qInfo() << "[COORDINATOR]Print the current transaction";

    QThread::msleep(1000);

    m_last_saved_trans.user_alias = m_datastorage->db_op_station_id(m_last_saved_trans.user_name);
    emit print(m_last_saved_trans);
}

void Coordinator::print_transaction(uint32_t rec_idx)
{
    TransInfo trans_info;

    qInfo().noquote() << QString("[COORDINATOR]Print %1th transaction of inquiry data").arg(rec_idx);

    if (m_datastorage->db_query_nth(rec_idx, trans_info)) {
        trans_info.user_alias = m_datastorage->db_op_station_id(trans_info.user_name);
        emit print(trans_info);
    }
}

void Coordinator::print_transactions()
{
    uint32_t rec_idx;
    TransInfo trans_info;
    QList<TransInfo> trans_list;

    qInfo().noquote() << QString("[COORDINATOR]Print all transaction of inquiry data");

    rec_idx = 1;
    while (m_datastorage->db_query_nth(rec_idx, trans_info)) {
        trans_list.append(trans_info);
        rec_idx++;
    }
    if (trans_list.count())
        emit print_all(trans_list);
}

void Coordinator::print_clr_transaction(uint32_t rec_idx)
{
    ClearInfo clr_info;

    qInfo().noquote() << QString("[COORDINATOR]Print %1th clear transaction of inquiry data").arg(rec_idx);

    if (m_datastorage->db_clr_query_nth(rec_idx, clr_info))
        emit clr_print(clr_info);
}

void Coordinator::start_clear_deposit()
{
    ClearInfo clr_info;

    m_datastorage->db_total_transaction(clr_info);
    emit clr_total_show(clr_info);
}

void Coordinator::clear_deposit()
{
    QSettings settings(JSONSettings::GetSettingFilePath(), JSONSettings::GetFormat(), QCoreApplication::instance());
    m_curr_amount_limit = m_max_amount_limit;
    settings.setValue(JSON_WORK_CURR_AMOUNT_LIMIT, m_curr_amount_limit);

    m_datastorage->db_clear_transaction();
    update_transaction_summary();
}

void Coordinator::transaction_uploaded(TransInfo trans_info)
{
    m_datastorage->db_transaction_uploaded(trans_info.id);
}

void Coordinator::store_gate_status(uint16_t status)
{
    Q_UNUSED(status)
}

void Coordinator::sw_upgrade(QString link, QString file_name, QString new_version)
{
    qInfo() << "[COORDINATOR]Current Software Version:" << sw_version();
    qInfo() << "[COORDINATOR]Updagre Software Version:" << new_version;
    if (new_version > sw_version()) {
        qInfo() << "[COORDINATOR]Download software package and Upgrade..." << link;
        QString program = "./upgrade.sh";
        QStringList arguments;
        arguments << link;
        arguments << file_name;
        QProcess::startDetached(program, arguments);
    }
}

void Coordinator::machine_info(QString name, QString server_name, QString terminal_id)
{
    QSettings settings(JSONSettings::GetSettingFilePath(), JSONSettings::GetFormat(), QCoreApplication::instance());

    settings.setValue(JSON_DEVICE_TERMINAL_NAME, name);
    settings.setValue(JSON_HTTP_SERVER_IP, server_name);
    settings.setValue(JSON_DEVICE_TERMINAL_ID, terminal_id);

    m_terminal_name = name;
    m_http_server_name = server_name;
    m_terminal_id = terminal_id;

    emit set_machine_info(m_terminal_name, m_http_server_name, m_terminal_id);
}

void Coordinator::inquiry_clr_data(QDate start_date, QDate stop_date)
{
    ClearInfo clr_info;

    qInfo() << "[COORDINATOR]Data Inquiry (Date:" << start_date << "to:" << stop_date << ")";

    m_datastorage->db_clr_query(start_date, stop_date);

    QStringList data_list;
    while (m_datastorage->db_clr_query_next(clr_info)) {
        QString data;

        data += clr_info.date_time.toString("yyyy-MM-dd") + ";";
        data += clr_info.date_time.toString("hh:mm:ss") + ";";
        data += QString("%1;").arg(clr_info.note_amount / 100.0, 0, 'f', 2);
        data += QString("%1;").arg(clr_info.coin_amount / 100.0, 0, 'f', 2);
        data += QString("%1;").arg(clr_info.amount / 100.0, 0, 'f', 2);

        data_list << data;
    }
    emit clr_data_show(data_list); 
}

uint32_t Coordinator::gpio_get(QString dev_name)
{
    char c = '0';

#ifdef __aarch64__
    QFile dev(dev_name);
    if (dev.open(QIODevice::ReadOnly))
        dev.getChar(&c);
#endif

    return c - '0';
}

uint32_t Coordinator::save_transaction(uint32_t trans_type)
{
    uint32_t note_count;

    QSettings settings(JSONSettings::GetSettingFilePath(), JSONSettings::GetFormat(), QCoreApplication::instance());

    m_curr_trans.date_time = QDateTime::currentDateTime();
    m_curr_trans.user_name = m_user_name;
    m_curr_trans.client_name = m_client_name;
    m_curr_trans.terminal_name = m_terminal_name;
    m_curr_trans.terminal_id = m_terminal_id;
    m_curr_trans.note_info.trans_type = trans_type;
    m_curr_trans.status = TRANS_STATUS_NOT_UPLOADED;

    m_datastorage->db_append_sub_transaction(m_curr_trans);

    // update the amount limit and clear the transaction;
    m_curr_amount_limit -= m_curr_trans.note_info.total_amount;
    settings.setValue(JSON_WORK_CURR_AMOUNT_LIMIT, m_curr_amount_limit);
    note_count = m_curr_trans.note_info.total_count;
    memset(&m_curr_trans.note_info, 0, sizeof(NoteInfo));

    return note_count;
}

void Coordinator::save_failed_transaction(uint32_t trans_type)
{
    m_curr_trans.date_time = QDateTime::currentDateTime();
    m_curr_trans.user_name = m_user_name;
    m_curr_trans.client_name = m_client_name;
    m_curr_trans.terminal_name = m_terminal_name;
    m_curr_trans.terminal_id = m_terminal_id;
    m_curr_trans.note_info.trans_type = trans_type;
    m_curr_trans.status = TRANS_STATUS_NOT_UPLOADED;

    m_datastorage->db_append_failed_sub_transaction(m_curr_trans);
}

void Coordinator::update_transaction_summary()
{
    ClearInfo clr_info;

    m_datastorage->db_total_transaction(clr_info);
    m_dev_status.note_amount = clr_info.note_amount;
    m_dev_status.note_count = clr_info.note_count;
    m_dev_status.coin_amount = clr_info.coin_amount;
    m_dev_status.coin_count = clr_info.coin_count;
}

void Coordinator::set_task(TaskType curr, TaskType next, uint32_t timeout)
{
    m_task.curr_task = curr;
    m_task.next_task = next;
    m_task.waiting_time = timeout;
    m_task.store_completed = false;
    m_task.store_exception = 0;
    m_task.count_started = false;
    m_task.count_completed = false;
    m_task.count_exception = 0;
    m_task.inquiry_count = 0;

    m_stop_inquiry_countdown = 20;
}

void Coordinator::fp_cleanup()
{
    int next_fp_avail_id;

    next_fp_avail_id = m_fp_controller->getWorker()->avail_id();
    for (int k = 1; k < next_fp_avail_id; k++) {
        if (m_datastorage->db_op_exists(k))
            continue;
        qInfo() << "[FINGERPRINT]Remove Template" << k;
        emit fp_delete(k);
    }
}
